{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# dislocation_periodic_array calculation style\n",
    "\n",
    "**Lucas M. Hale**, [lucas.hale@nist.gov](mailto:lucas.hale@nist.gov?Subject=ipr-demo), *Materials Science and Engineering Division, NIST*.\n",
    "\n",
    "\n",
    "## Introduction\n",
    "\n",
    "The dislocation_periodic_array calculation constructs an atomic system with a periodic array of dislocations configuration.  A single dislocation is inserted into an otherwise perfect crystal, and the system is kept periodic in the two system box directions that are within the dislocation's slip plane.  The system is then statically relaxed with the atoms at the boundary perpendicular to the slip plane held fixed.  \n",
    "\n",
    "### Version notes\n",
    "\n",
    "- 2019-07-30: Notebook added.\n",
    "- 2020-05-22: Notebook updated for iprPy version 0.10 and tested for hcp\n",
    "- 2020-09-22: Notebook updated to reflect that calculation method has changed to now use atomman.defect.Dislocation. Setup and parameter definition cleaned up and streamlined. \n",
    "\n",
    "### Additional dependencies\n",
    "\n",
    "### Disclaimers\n",
    "\n",
    "- [NIST disclaimers](http://www.nist.gov/public_affairs/disclaimer.cfm)\n",
    "- This calculation was designed to be general enough to properly generate a dislocation for any crystal system but has not been fully tested yet for extreme cases. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Method and Theory\n",
    "\n",
    "### System orientation considerations\n",
    "\n",
    "Properly constructing a periodic array of dislocations atomic configuration requires careful consideration of dislocation solutions and atomic system boundaries.  Solutions for straight dislocations based on elasticity often follow the convention of using a Cartesian system ($x', y', z'$) in which the dislocation line is oriented along the $z'$-axis, and the slip plane taken to be the $y'=0$ plane. The dislocation's Burgers vector, $\\vec{b}$, is then in the $x'z'$-plane, with edge component in the $x'$-direction and screw component in the $z'$-direction.  When the dislocation slips, the dislocation line will move in the $x'$-direction.\n",
    "\n",
    "For any such dislocation solution, there will be a shearing along the slip plane resulting in disregistry, i.e. a relative displacement between the top and bottom halves.  This disregistry has limits such that it is $0$ for $x' \\to -\\infty$ and $\\vec{b}$ for $x' \\to +\\infty$.\n",
    "\n",
    "Within an atomic system, the dislocation line should be aligned with one of the system's box vectors making the dislocation infinitely long and initially perfectly straight.  The slip plane can then be defined as containing that box vector and another one.  This results in the third box vector being the only one with a component parallel to the slip plane's normal.\n",
    "\n",
    "For LAMMPS-based simulations, the most convenient orientation to use is to align the dislocation with the $\\vec{a}$ box vector, and to define the slip plane as containing both $\\vec{a}$ and $\\vec{b}$.  Given the limits that LAMMPS places on how system boxes can be defined, this results in favorable alignment of the system to the LAMMPS Cartesian system ($x, y, z$). The dislocation line will be along the $x$-axis, the slip plane normal parallel to the $z$-axis, and dislocation motion will be in the $y$ direction. Thus, the LAMMPS coordinates corresponds to a rotation of the theory coordinates such that $x'=y, y'=z, z'=x$.\n",
    "\n",
    "### Linear displacements solution\n",
    "\n",
    "The simplest way to insert a dislocation is to cut the system in half along the slip plane and apply equal but opposite linear displacements, $\\vec{u}$, to the two halves with end conditions\n",
    "\n",
    "- $\\vec{u}(y=-\\frac{Ly}{2}) = 0$\n",
    "- $\\vec{u}(y=\\frac{Ly}{2}) = \\pm \\frac{\\vec{b}}{2}$\n",
    "\n",
    "Applying these displacements results in a disregistry along the slip plane that ranges from $0$ to $\\vec{b}$.  While the two $y$ boundaries of the system both correspond to a perfect crystal, they are misaligned from each other by $\\frac{\\vec{b}}{2}$.  A coherent periodic boundary along the $\\vec{b}$ box vector can be established by adding or subtracting $\\frac{\\vec{b}}{2}$ from $\\vec{b}$.  \n",
    "\n",
    "Note that with dislocations containing an edge component, a half-plane of atoms either needs to be inserted or removed to ensure boundary compatibility. Here, this is accomplished by always shifting $\\vec{b}$ to be shorter in the $y$ direction, and removing excess atoms by identifying (near) duplicates.\n",
    "\n",
    "### Using dislocation solutions\n",
    "\n",
    "A slightly more complicated, but ultimately more efficient, way of creating a periodic array of dislocations system is to combine the linear displacements solultion above with a more accurate linear elastic dislocation solution.  The linear solution is used for the atoms at the free surfaces in the $z$ direction, and for ensuring periodicity across the $\\vec{b}$ box vector direction.  The linear elastic dislocation solution is then used for atoms in the middle of the system to construct an initial dislocation.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Demonstration"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.1. Library imports"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Import libraries needed by the calculation. The external libraries used are:\n",
    "\n",
    "- [numpy](http://www.numpy.org/)\n",
    "\n",
    "- [atomman](https://github.com/usnistgov/atomman)\n",
    "\n",
    "- [iprPy](https://github.com/usnistgov/iprPy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Notebook last executed on 2020-09-22 using iprPy version 0.10.2\n"
     ]
    }
   ],
   "source": [
    "# Standard library imports\n",
    "from pathlib import Path\n",
    "import os\n",
    "import random\n",
    "import datetime\n",
    "\n",
    "# http://www.numpy.org/\n",
    "import numpy as np \n",
    "\n",
    "# https://github.com/usnistgov/atomman \n",
    "import atomman as am\n",
    "import atomman.lammps as lmp\n",
    "import atomman.unitconvert as uc\n",
    "\n",
    "# https://github.com/usnistgov/iprPy\n",
    "import iprPy\n",
    "\n",
    "print('Notebook last executed on', datetime.date.today(), 'using iprPy version', iprPy.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1.2. Default calculation setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Specify calculation style\n",
    "calc_style = 'dislocation_periodic_array'\n",
    "\n",
    "# If workingdir is already set, then do nothing (already in correct folder)\n",
    "try:\n",
    "    workingdir = workingdir\n",
    "\n",
    "# Change to workingdir if not already there\n",
    "except:\n",
    "    workingdir = Path('calculationfiles', calc_style)\n",
    "    if not workingdir.is_dir():\n",
    "        workingdir.mkdir(parents=True)\n",
    "    os.chdir(workingdir)\n",
    "    \n",
    "# Initialize connection to library\n",
    "with open('C:/Users/lmh1/Documents/potentials_nist_gov/password.txt') as f:\n",
    "    user, pswd = f.read().strip().split()\n",
    "library = iprPy.Library(load=['lammps_potentials'], username=user, password=pswd)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Assign values for the calculation's run parameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.1. Specify system-specific paths\n",
    "\n",
    "- __lammps_command__ (required) is the LAMMPS command to use.\n",
    "\n",
    "- __mpi_command__ MPI command for running LAMMPS in parallel. A value of None will run simulations serially."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "lammps_command = 'lmp_serial'\n",
    "mpi_command = None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.2. Load interatomic potential\n",
    "\n",
    "- __potential_name__ gives the name of the potential_LAMMPS reference record in the iprPy library to use for the calculation.  \n",
    "\n",
    "- __potential_file__ gives the path to the potential_LAMMPS reference record to use.  Here, this parameter is automatically generated using potential_name and librarydir.\n",
    "\n",
    "- __potential_dir__ gives the path for the folder containing the artifacts associated with the potential (i.e. eam.alloy file).  Here, this parameter is automatically generated using potential_name and librarydir.\n",
    "\n",
    "- __potential__ is an atomman.lammps.Potential object (required).  Here, this parameter is automatically generated from potential_file and potential_dir."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "potential_name = '1999--Mishin-Y--Ni--LAMMPS--ipr1'\n",
    "\n",
    "# Retrieve potential and parameter file(s)\n",
    "potential = library.get_lammps_potential(id=potential_name, getfiles=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.3. Load initial unit cell system\n",
    "\n",
    "- __ucell__ is an atomman.System representing a fundamental unit cell of the system (required).  Here, this is loaded from the database for the prototype."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "avect =  [ 3.520,  0.000,  0.000]\n",
      "bvect =  [ 0.000,  3.520,  0.000]\n",
      "cvect =  [ 0.000,  0.000,  3.520]\n",
      "origin = [ 0.000,  0.000,  0.000]\n",
      "natoms = 4\n",
      "natypes = 1\n",
      "symbols = ('Ni',)\n",
      "pbc = [ True  True  True]\n",
      "per-atom properties = ['atype', 'pos']\n",
      "     id |   atype |  pos[0] |  pos[1] |  pos[2]\n",
      "      0 |       1 |   0.000 |   0.000 |   0.000\n",
      "      1 |       1 |   0.000 |   1.760 |   1.760\n",
      "      2 |       1 |   1.760 |   0.000 |   1.760\n",
      "      3 |       1 |   1.760 |   1.760 |   0.000\n"
     ]
    }
   ],
   "source": [
    "# Create ucell by loading prototype record\n",
    "ucell = am.load('crystal', potential=potential, family='A1--Cu--fcc', database=library)\n",
    "\n",
    "print(ucell)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.4 Specify material elastic constants\n",
    "\n",
    "Simple input parameters:\n",
    "\n",
    "- __C_dict__ is a dictionary containing the unique elastic constants for the potential and crystal structure defined above. \n",
    "\n",
    "Derived parameters\n",
    "\n",
    "- __C__ is an atomman.ElasticConstants object built from C_dict."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "C_dict = {}\n",
    "C_dict['C11'] = uc.set_in_units(247.86, 'GPa')\n",
    "C_dict['C12'] = uc.set_in_units(147.83, 'GPa')\n",
    "C_dict['C44'] = uc.set_in_units(124.84, 'GPa')\n",
    "\n",
    "# -------------- Derived parameters -------------- #\n",
    "# Build ElasticConstants object from C_dict terms\n",
    "C = am.ElasticConstants(**C_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.5 Specify the defect parameters\n",
    "\n",
    "- __burgers__ is the crystallographic Miller Burgers vector for the dislocation. \n",
    "\n",
    "- __ξ_uvw__ is the Miller \\[uvw\\] line vector direction for the dislocation.  The angle between burgers and ξ_uvw determines the dislocation's character\n",
    "\n",
    "- __slip_hkl__ is the Miller (hkl) slip plane for the dislocation.\n",
    "    \n",
    "- __m__ is the Cartesian vector of the final system that the dislocation solution's m vector (in-plane, perpendicular to ξ) should align with.  Limited to being parallel to one of the three Cartesian axes.  \n",
    "\n",
    "- __n__ is the Cartesian vector of the final system that the dislocation solution's n vector (slip plane normal) should align with.  Limited to being parallel to one of the three Cartesian axes. \n",
    "\n",
    "- __shift__ is a rigid body shift to apply to the atoms in the system. This controls how the atomic positions align with the ideal position of the dislocation core, which is at coordinates (0,0) for the two Cartesian axes aligned with m and n.\n",
    "\n",
    "- __shiftscale__ allows for shift to be defined relative to the cell created by rotating ucell to coincide with the dislocation solution orientation.  This is useful as it allows for shift values to be defined relative to the defect type and crystal prototype rather than on a per-crystal basis. \n",
    "\n",
    "- __shiftindex__ alternate to specifying shift values, the shiftindex allows for one of the identified suggested shift values to be used that will position the slip plane halfway between two planes of atoms.  Note that shiftindex values only shift atoms in the slip plane normal direction and may not be the ideal positions for some dislocation cores."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# fcc a/2 <110>{111} dislocations\n",
    "burgers = 0.5 * np.array([ 1., -1., 0.])\n",
    "slip_hkl = [1, 1, 1]\n",
    "\n",
    "# Line direction determines dislocation character\n",
    "ξ_uvw = [ 1, 1,-2] # 90 degree edge\n",
    "#ξ_uvw = [ 1, 0,-1] # 60 degree mixed\n",
    "#ξ_uvw = [ 1,-2, 1] # 30 degree mixed\n",
    "#ξ_uvw = [ 1,-1, 0] # 0 degree screw\n",
    "\n",
    "# Best choice for m + n as it works for non-cubic systems\n",
    "m = [0,1,0]\n",
    "n = [0,0,1]\n",
    "\n",
    "# Specify shift or shiftindex\n",
    "shift = None\n",
    "shiftscale = True\n",
    "shiftindex = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.6 Specify calculation-specific run parameters\n",
    "\n",
    "Simple input parameters:\n",
    "\n",
    "- __boundarywidth__ sets the minimum width of the fixed-atom boundary region.\n",
    "\n",
    "- __boundaryscale__ flag indicating if boundarywidth is absolute (False) or scaled relative to ucell's a lattice parameter (True).\n",
    "\n",
    "- __duplicatecutoff__ Cutoff distance to use for identifying duplicate atoms to remove.  For dislocations with an edge component, applying the displacements creates an extra half-plane of atoms that will have (nearly) identical positions with other atoms after altering the boundary conditions. \n",
    "\n",
    "- __onlylinear__ boolean flag. If False (default) the atoms in the middle of the system will be displaced according to an elasticity solution for a dislocation core and boundary atoms displaced by a linear gradient.  If True, all atoms are displaced by the linear gradient which may be necessary for pure screw dislocations. \n",
    "\n",
    "- __annealtemperature__ is the temperature at which to relax (anneal) the dislocation system. If annealtemperature is 0.0, then only a static relaxation will be performed. Default value is 0.0.\n",
    "\n",
    "- __annealsteps__ is the number of nvt iteration steps to perform at the given temperature.  Default value is 0 if annealtemperature is zero, 10000 otherwise.\n",
    "\n",
    "- __randomseed__ allows for the random seed used in generating initial atomic velocities for a dynamic relaxation to be specified. This is an integer between 1 and 900000000. Default value is None, which will randomly pick a number in that range.\n",
    "\n",
    "- __energytolerance__ is the energy tolerance to use during the minimizations. This is unitless.\n",
    "\n",
    "- __forcetolerance__ is the force tolerance to use during the minimizations. This is in energy/length units.\n",
    "\n",
    "- __maxiterations__ is the maximum number of minimization iterations to use.\n",
    "\n",
    "- __maxevaluations__ is the maximum number of minimization evaluations to use.\n",
    "\n",
    "- __maxatommotion__ is the largest distance that an atom is allowed to move during a minimization iteration. This is in length units. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "boundarywidth = 3\n",
    "boundaryscale = True\n",
    "duplicatecutoff = uc.set_in_units(0.5, 'angstrom')\n",
    "onlylinear = False\n",
    "\n",
    "annealtemperature = 50.0\n",
    "annealsteps = 10000\n",
    "randomseed = None\n",
    "\n",
    "energytolerance = 0.0\n",
    "forcetolerance = uc.set_in_units(1e-6, 'eV/angstrom')\n",
    "maxiterations = 10000\n",
    "maxevaluations = 100000\n",
    "maxatommotion = uc.set_in_units(0.01, 'angstrom')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.7. Modify system\n",
    "\n",
    "- __sizemults__ list of three sets of two integers specifying how many times the ucell vectors of $a$, $b$ and $c$ are replicated in positive and negative directions when creating system.\n",
    "\n",
    "- __amin, bmin, cmin__ specifies minimum widths in the three directions. The corresponding sizemults will be increased to ensure these widths if needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "sizemults = [1, 40, 40]\n",
    "amin = uc.set_in_units(0.0, 'angstrom')\n",
    "bmin = uc.set_in_units(0.0, 'angstrom')\n",
    "cmin = uc.set_in_units(0.0, 'angstrom')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Define calculation function(s) and generate template LAMMPS script(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.1. disl_relax.template"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('disl_relax.template', 'w') as f:\n",
    "    f.write(\"\"\"#LAMMPS input script for relaxing a dislocation\n",
    "\n",
    "<atomman_system_pair_info>\n",
    "\n",
    "group move type <group_move>\n",
    "group hold subtract all move\n",
    "\n",
    "compute peatom all pe/atom\n",
    "\n",
    "dump first all custom <maxeval> *.dump id type x y z c_peatom\n",
    "dump_modify first format <dump_modify_format>\n",
    "thermo_style custom step pe\n",
    "\n",
    "fix nomove hold setforce 0.0 0.0 0.0\n",
    "\n",
    "<anneal_info>\n",
    "\n",
    "min_modify dmax <dmax>\n",
    "\n",
    "minimize <etol> <ftol> <maxiter> <maxeval>\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.2. dislocationarray()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def dislocationarray(lammps_command, ucell, potential, C, burgers, ξ_uvw,\n",
    "                     slip_hkl, mpi_command=None, m=[0,1,0], n=[0,0,1],\n",
    "                     sizemults=None, amin=None, bmin=None, cmin=None,\n",
    "                     shift=None, shiftscale=False, shiftindex=None, tol=1e-8,\n",
    "                     etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000,\n",
    "                     dmax=uc.set_in_units(0.01, 'angstrom'),\n",
    "                     annealtemp=0.0, annealsteps=None, randomseed=None,\n",
    "                     boundaryshape='cylinder', boundarywidth=0.0, boundaryscale=False,\n",
    "                     cutoff=None, linear=False):\n",
    "    \"\"\"\n",
    "    Creates and relaxes a dislocation monopole system.\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    lammps_command :str\n",
    "        Command for running LAMMPS.\n",
    "    ucell : atomman.System\n",
    "        The unit cell to use as the seed for generating the dislocation\n",
    "        monopole system.\n",
    "    potential : atomman.lammps.Potential\n",
    "        The LAMMPS implemented potential to use.\n",
    "    C : atomman.ElasticConstants\n",
    "        The elastic constants associated with the bulk crystal structure\n",
    "        for ucell.\n",
    "    burgers : array-like object\n",
    "        The dislocation's Burgers vector given as a Miller or\n",
    "        Miller-Bravais vector relative to ucell.\n",
    "    ξ_uvw : array-like object\n",
    "        The dislocation's line direction given as a Miller or\n",
    "        Miller-Bravais vector relative to ucell.\n",
    "    slip_hkl : array-like object\n",
    "        The dislocation's slip plane given as a Miller or Miller-Bravais\n",
    "        plane relative to ucell.\n",
    "    mpi_command : str or None, optional\n",
    "        The MPI command for running LAMMPS in parallel.  If not given, LAMMPS\n",
    "        will run serially.\n",
    "    m : array-like object, optional\n",
    "        The m unit vector for the dislocation solution.  m, n, and ξ\n",
    "        (dislocation line) should be right-hand orthogonal.  Default value\n",
    "        is [0,1,0] (y-axis).\n",
    "    n : array-like object, optional\n",
    "        The n unit vector for the dislocation solution.  m, n, and ξ\n",
    "        (dislocation line) should be right-hand orthogonal.  Default value\n",
    "        is [0,0,1] (z-axis). n is normal to the dislocation slip plane.\n",
    "    sizemults : tuple, optional\n",
    "        The size multipliers to use when generating the system.  Values are\n",
    "        limited to being positive integers.  The multipliers for the two\n",
    "        non-periodic directions must be even.  If not given, the default\n",
    "        multipliers will be 2 for the non-periodic directions and 1 for the\n",
    "        periodic direction.\n",
    "    amin : float, optional\n",
    "        A minimum thickness to use for the a box vector direction of the\n",
    "        final system.  Default value is 0.0.  For the non-periodic\n",
    "        directions, the resulting vector multiplier will be even.  If both\n",
    "        amin and sizemults is given, then the larger multiplier for the two\n",
    "        will be used.\n",
    "    bmin : float, optional\n",
    "        A minimum thickness to use for the b box vector direction of the\n",
    "        final system.  Default value is 0.0.  For the non-periodic\n",
    "        directions, the resulting vector multiplier will be even.  If both\n",
    "        bmin and sizemults is given, then the larger multiplier for the two\n",
    "        will be used.\n",
    "    cmin : float, optional\n",
    "        A minimum thickness to use for the c box vector direction of the\n",
    "        final system.  Default value is 0.0.  For the non-periodic\n",
    "        directions, the resulting vector multiplier will be even.  If both\n",
    "        cmin and sizemults is given, then the larger multiplier for the two\n",
    "        will be used.\n",
    "    shift : float, optional\n",
    "        A rigid body shift to apply to the rotated cell prior to inserting\n",
    "        the dislocation.  Should be selected such that the ideal slip plane\n",
    "        does not correspond to any atomic planes.  Is taken as absolute if\n",
    "        shiftscale is False, or relative to the rotated cell's box vectors\n",
    "        if shiftscale is True.  Cannot be given with shiftindex.  If\n",
    "        neither shift nor shiftindex is given then shiftindex = 0 is used.\n",
    "    shiftindex : float, optional\n",
    "        The index of the identified optimum shifts based on the rotated\n",
    "        cell to use.  Different values allow for the selection of different\n",
    "        atomic planes neighboring the slip plane.  Note that shiftindex\n",
    "        values only apply shifts normal to the slip plane; best shifts for\n",
    "        non-planar dislocations (like bcc screw) may also need a shift in\n",
    "        the slip plane.  Cannot be given with shiftindex.  If neither shift\n",
    "        nor shiftindex is given then shiftindex = 0 is used.\n",
    "    shiftscale : bool, optional\n",
    "        If False (default), a given shift value will be taken as absolute\n",
    "        Cartesian.  If True, a given shift will be taken relative to the\n",
    "        rotated cell's box vectors.\n",
    "    tol : float\n",
    "        A cutoff tolerance used with obtaining the dislocation solution.\n",
    "        Only needs to be changed if there are issues with obtaining a\n",
    "        solution.\n",
    "    etol : float, optional\n",
    "        The energy tolerance for the structure minimization. This value is\n",
    "        unitless. Default is 0.0.\n",
    "    ftol : float, optional\n",
    "        The force tolerance for the structure minimization. This value is in\n",
    "        units of force. Default is 0.0.\n",
    "    maxiter : int, optional\n",
    "        The maximum number of minimization iterations to use. Default is \n",
    "        10000.\n",
    "    maxeval : int, optional\n",
    "        The maximum number of minimization evaluations to use. Default is \n",
    "        100000.\n",
    "    dmax : float, optional\n",
    "        The maximum distance in length units that any atom is allowed to relax\n",
    "        in any direction during a single minimization iteration. Default is\n",
    "        0.01 Angstroms.\n",
    "    annealtemp : float, optional\n",
    "        The temperature to perform a dynamic relaxation at. Default is 0.0,\n",
    "        which will skip the dynamic relaxation.\n",
    "    annealsteps : int, optional\n",
    "        The number of time steps to run the dynamic relaxation for.  Default\n",
    "        is None, which will run for 10000 steps if annealtemp is not 0.0.  \n",
    "    randomseed : int or None, optional\n",
    "        Random number seed used by LAMMPS in creating velocities and with\n",
    "        the Langevin thermostat.  Default is None which will select a\n",
    "        random int between 1 and 900000000.\n",
    "    boundarywidth : float, optional\n",
    "        The width of the boundary region to apply.  Default value is 0.0,\n",
    "        i.e. no boundary region.  All atoms in the boundary region will\n",
    "        have their atype values changed.\n",
    "    boundaryscale : bool, optional\n",
    "        If False (Default), the boundarywidth will be taken as absolute.\n",
    "        If True, the boundarywidth will be taken relative to the magnitude\n",
    "        of the unit cell's a box vector.\n",
    "    linear : bool, optional\n",
    "        If True, then only linear displacements will be used and not the\n",
    "        dislocation solution.  Using only linear displacements is useful\n",
    "        for screw dislocations and dislocations with large stacking fault\n",
    "        distances.  If False (default) then the dislocation solution will\n",
    "        be used for the middle displacements and linear displacements only\n",
    "        in the boundary region.\n",
    "    cutoff : float, optional\n",
    "        Cutoff distance to use for identifying duplicate atoms to remove.\n",
    "        For dislocations with an edge component, applying the displacements\n",
    "        creates an extra half-plane of atoms that will have (nearly)\n",
    "        identical positions with other atoms after altering the boundary\n",
    "        conditions.  Default value is 0.5 Angstrom.\n",
    "    \n",
    "    Returns\n",
    "    -------\n",
    "    dict\n",
    "        Dictionary of results consisting of keys:\n",
    "        \n",
    "        - **'dumpfile_base'** (*str*) - The filename of the LAMMPS dump file\n",
    "          for the relaxed base system.\n",
    "        - **'symbols_base'** (*list of str*) - The list of element-model\n",
    "          symbols for the Potential that correspond to the base system's\n",
    "          atypes.\n",
    "        - **'dumpfile_disl'** (*str*) - The filename of the LAMMPS dump file\n",
    "          for the relaxed dislocation monopole system.\n",
    "        - **'symbols_disl'** (*list of str*) - The list of element-model\n",
    "          symbols for the Potential that correspond to the dislocation\n",
    "          monopole system's atypes.\n",
    "        - **'dislocation'** (*atomman.defect.Dislocation*) - The Dislocation\n",
    "          object used to generate the monopole system.\n",
    "        - **'E_total_disl'** (*float*) - The total potential energy of the\n",
    "          dislocation monopole system.\n",
    "    \"\"\"\n",
    "    # Construct dislocation configuration generator\n",
    "    dislocation = am.defect.Dislocation(ucell, C, burgers, ξ_uvw, slip_hkl,\n",
    "                                        m=m, n=n, shift=shift, shiftindex=shiftindex,\n",
    "                                        shiftscale=shiftscale, tol=tol)\n",
    "    \n",
    "    # Generate the base and dislocation systems\n",
    "    base_system, disl_system = dislocation.periodicarray(sizemults=sizemults,\n",
    "                                                         amin=amin, bmin=bmin, cmin=cmin,\n",
    "                                                         shift=shift,\n",
    "                                                         shiftindex=shiftindex,\n",
    "                                                         shiftscale=shiftscale,\n",
    "                                                         boundarywidth=boundarywidth,\n",
    "                                                         boundaryscale=boundaryscale,\n",
    "                                                         linear=linear,\n",
    "                                                         cutoff=cutoff,\n",
    "                                                         return_base_system=True)\n",
    "    \n",
    "    # Initialize results dict\n",
    "    results_dict = {}\n",
    "    \n",
    "    # Save initial perfect system\n",
    "    base_system.dump('atom_dump', f='base.dump')\n",
    "    results_dict['dumpfile_base'] = 'base.dump'\n",
    "    results_dict['symbols_base'] = base_system.symbols\n",
    "    \n",
    "    # Save dislocation generator\n",
    "    results_dict['dislocation'] = dislocation\n",
    "\n",
    "    # Relax system\n",
    "    relaxed = disl_relax(lammps_command, disl_system, potential,\n",
    "                         mpi_command=mpi_command, annealtemp=annealtemp,\n",
    "                         annealsteps=annealsteps, randomseed=randomseed,\n",
    "                         etol=etol, ftol=ftol, maxiter=maxiter, \n",
    "                         maxeval=maxeval, dmax=dmax)\n",
    "    \n",
    "    # Save relaxed dislocation system with original box vects\n",
    "    system_disl = am.load('atom_dump', relaxed['dumpfile'], symbols=disl_system.symbols)\n",
    "    \n",
    "    system_disl.box_set(vects=disl_system.box.vects, origin=disl_system.box.origin)\n",
    "    system_disl.dump('atom_dump', f='disl.dump')\n",
    "    results_dict['dumpfile_disl'] = 'disl.dump'\n",
    "    results_dict['symbols_disl'] = system_disl.symbols\n",
    "    \n",
    "    results_dict['E_total_disl'] = relaxed['E_total']\n",
    "    \n",
    "    # Cleanup files\n",
    "    Path('0.dump').unlink()\n",
    "    Path(relaxed['dumpfile']).unlink()\n",
    "    for dumpjsonfile in Path('.').glob('*.dump.json'):\n",
    "        dumpjsonfile.unlink()\n",
    "    \n",
    "    return results_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.3 disl_relax() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def disl_relax(lammps_command, system, potential,\n",
    "               mpi_command=None, \n",
    "               annealtemp=0.0, annealsteps=None, randomseed=None,\n",
    "               etol=0.0, ftol=1e-6, maxiter=10000, maxeval=100000,\n",
    "               dmax=uc.set_in_units(0.01, 'angstrom')):\n",
    "    \"\"\"\n",
    "    Sets up and runs the disl_relax.in LAMMPS script for relaxing a\n",
    "    dislocation monopole system.\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    lammps_command :str\n",
    "        Command for running LAMMPS.\n",
    "    system : atomman.System\n",
    "        The system to perform the calculation on.\n",
    "    potential : atomman.lammps.Potential\n",
    "        The LAMMPS implemented potential to use.\n",
    "    mpi_command : str, optional\n",
    "        The MPI command for running LAMMPS in parallel.  If not given, LAMMPS\n",
    "        will run serially.\n",
    "    annealtemp : float, optional\n",
    "        The temperature to perform a dynamic relaxation at. Default is 0.0,\n",
    "        which will skip the dynamic relaxation.\n",
    "    annealsteps : int, optional\n",
    "        The number of time steps to run the dynamic relaxation for.  Default\n",
    "        is None, which will run for 10000 steps if annealtemp is not 0.0.        \n",
    "    randomseed : int or None, optional\n",
    "        Random number seed used by LAMMPS in creating velocities and with\n",
    "        the Langevin thermostat.  Default is None which will select a\n",
    "        random int between 1 and 900000000.\n",
    "    etol : float, optional\n",
    "        The energy tolerance for the structure minimization. This value is\n",
    "        unitless. Default is 0.0.\n",
    "    ftol : float, optional\n",
    "        The force tolerance for the structure minimization. This value is in\n",
    "        units of force. Default is 0.0.\n",
    "    maxiter : int, optional\n",
    "        The maximum number of minimization iterations to use default is \n",
    "        10000.\n",
    "    maxeval : int, optional\n",
    "        The maximum number of minimization evaluations to use default is \n",
    "        100000.\n",
    "    dmax : float, optional\n",
    "        The maximum distance in length units that any atom is allowed to relax\n",
    "        in any direction during a single minimization iteration default is\n",
    "        0.01 Angstroms.\n",
    "        \n",
    "    Returns\n",
    "    -------\n",
    "    dict\n",
    "        Dictionary of results consisting of keys:\n",
    "        \n",
    "        - **'logfile'** (*str*) - The name of the LAMMPS log file.\n",
    "        - **'dumpfile'** (*str*) - The name of the LAMMPS dump file\n",
    "          for the relaxed system.\n",
    "        - **'E_total'** (*float*) - The total potential energy for the\n",
    "          relaxed system.\n",
    "    \"\"\"\n",
    "    # Build filedict if function was called from iprPy\n",
    "    try:\n",
    "        assert __name__ == pkg_name\n",
    "        calc = iprPy.load_calculation(calculation_style)\n",
    "        filedict = calc.filedict\n",
    "    except:\n",
    "        filedict = {}\n",
    "\n",
    "    # Get lammps units\n",
    "    lammps_units = lmp.style.unit(potential.units)\n",
    "    \n",
    "    #Get lammps version date\n",
    "    lammps_date = lmp.checkversion(lammps_command)['date']\n",
    "    \n",
    "    # Define lammps variables\n",
    "    lammps_variables = {}\n",
    "    system_info = system.dump('atom_data', f='system.dat',\n",
    "                              potential=potential,\n",
    "                              return_pair_info=True)\n",
    "    lammps_variables['atomman_system_pair_info'] = system_info\n",
    "    lammps_variables['anneal_info'] = anneal_info(annealtemp, annealsteps, \n",
    "                                                  randomseed, potential.units)\n",
    "    lammps_variables['etol'] = etol\n",
    "    lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force'])\n",
    "    lammps_variables['maxiter'] = maxiter\n",
    "    lammps_variables['maxeval'] = maxeval\n",
    "    lammps_variables['dmax'] = dmax\n",
    "    lammps_variables['group_move'] = ' '.join(np.array(range(1, system.natypes // 2 + 1), dtype=str))\n",
    "    \n",
    "    # Set dump_modify format based on dump_modify_version\n",
    "    if lammps_date < datetime.date(2016, 8, 3):\n",
    "        lammps_variables['dump_modify_format'] = '\"%d %d %.13e %.13e %.13e %.13e\"'\n",
    "    else:\n",
    "        lammps_variables['dump_modify_format'] = 'float %.13e'\n",
    "    \n",
    "    # Write lammps input script\n",
    "    template_file = 'disl_relax.template'\n",
    "    lammps_script = 'disl_relax.in'\n",
    "    template = iprPy.tools.read_calc_file(template_file, filedict)\n",
    "    with open(lammps_script, 'w') as f:\n",
    "        f.write(iprPy.tools.filltemplate(template, lammps_variables,\n",
    "                                         '<', '>'))\n",
    "    \n",
    "    # Run LAMMPS\n",
    "    output = lmp.run(lammps_command, lammps_script, mpi_command)\n",
    "    thermo = output.simulations[-1]['thermo']\n",
    "    \n",
    "    # Extract output values\n",
    "    results = {}\n",
    "    results['logfile'] = 'log.lammps'\n",
    "    results['dumpfile'] = '%i.dump' % thermo.Step.values[-1] \n",
    "    results['E_total'] = uc.set_in_units(thermo.PotEng.values[-1],\n",
    "                                         lammps_units['energy'])\n",
    "    \n",
    "    return results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 3.4 anneal_info() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def anneal_info(temperature=0.0, runsteps=None, randomseed=None, units='metal'):\n",
    "    \"\"\"\n",
    "    Generates LAMMPS commands for thermo anneal.\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    temperature : float, optional\n",
    "        The temperature to relax at (default is 0.0).\n",
    "    randomseed : int or None, optional\n",
    "        Random number seed used by LAMMPS in creating velocities and with\n",
    "        the Langevin thermostat.  (Default is None which will select a\n",
    "        random int between 1 and 900000000.)\n",
    "    units : str, optional\n",
    "        The LAMMPS units style to use (default is 'metal').\n",
    "    \n",
    "    Returns\n",
    "    -------\n",
    "    str\n",
    "        The generated LAMMPS input lines for performing a dynamic relax.\n",
    "        Will be '' if temperature==0.0.\n",
    "    \"\"\"    \n",
    "    # Return nothing if temperature is 0.0 (don't do thermo anneal)\n",
    "    if temperature == 0.0:\n",
    "        return ''\n",
    "    \n",
    "    # Generate velocity, fix nvt, and run LAMMPS command lines\n",
    "    else:\n",
    "        if randomseed is None:\n",
    "            randomseed = random.randint(1, 900000000)\n",
    "        if runsteps is None:\n",
    "            runsteps = 10000\n",
    "        \n",
    "        start_temp = 2 * temperature\n",
    "        tdamp = 100 * lmp.style.timestep(units)\n",
    "        timestep = lmp.style.timestep(units)\n",
    "        info = '\\n'.join([\n",
    "            'velocity move create %f %i mom yes rot yes dist gaussian' % (start_temp, randomseed),\n",
    "            'fix nvt all nvt temp %f %f %f' % (temperature, temperature,\n",
    "                                               tdamp),\n",
    "            'timestep %f' % (timestep),\n",
    "            'thermo %i' % (runsteps),\n",
    "            'run %i' % (runsteps),\n",
    "            ])\n",
    "    \n",
    "    return info"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Run calculation function(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "results_dict = dislocationarray(lammps_command, ucell, potential, C,\n",
    "                                burgers, ξ_uvw, slip_hkl,\n",
    "                                mpi_command = mpi_command,\n",
    "                                m = m,\n",
    "                                n = n,\n",
    "                                shift = shift,\n",
    "                                shiftscale = shiftscale,\n",
    "                                shiftindex = shiftindex,\n",
    "                                sizemults = sizemults,\n",
    "                                amin = amin,\n",
    "                                bmin = bmin,\n",
    "                                cmin = cmin,\n",
    "                                etol = energytolerance,\n",
    "                                ftol = forcetolerance,\n",
    "                                maxiter = maxiterations,\n",
    "                                maxeval = maxevaluations,\n",
    "                                dmax = maxatommotion,\n",
    "                                annealtemp = annealtemperature, \n",
    "                                annealsteps = annealsteps,\n",
    "                                randomseed = randomseed, \n",
    "                                boundarywidth = boundarywidth,\n",
    "                                boundaryscale = boundaryscale,   \n",
    "                                cutoff = duplicatecutoff,\n",
    "                                linear = onlylinear)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Report results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 5.1 List dump files"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Perfect system is saved as base.dump\n",
      "Defect system is saved as disl.dump\n"
     ]
    }
   ],
   "source": [
    "print('Perfect system is saved as', results_dict['dumpfile_base'])\n",
    "print('Defect system is saved as',  results_dict['dumpfile_disl'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
